import type { FormatOptions as DateFnsFormatOptions } from "date-fns";

import type { DateLibOptions } from "../../classes/DateLib.js";
import { toEthiopicDate } from "../utils/index.js";

import { formatNumber } from "./formatNumber.js";

/** Options for formatting dates in the Ethiopian calendar */
export type FormatOptions = DateFnsFormatOptions;

function getEtDayName(
  day: Date,
  short: boolean = true,
  localeCode: string = "am-ET",
): string {
  try {
    const dtf = new Intl.DateTimeFormat(localeCode, {
      // Ethiopic calendar expects single-letter for "cccccc" -> use narrow
      weekday: short ? ("narrow" as const) : ("long" as const),
    });
    return dtf.format(day);
  } catch {
    const dayOfWeek = day.getDay();
    return short ? shortDays[dayOfWeek] : longDays[dayOfWeek];
  }
}

function getEtMonthName(m: number, latin = false): string {
  if (m > 0 && m <= 13) {
    return latin ? ethMonthsLatin[m - 1] : ethMonths[m - 1];
  }
  return "";
}

function formatEthiopianDate(
  dateObj: Date | undefined,
  formatStr: string,
  numerals?: DateLibOptions["numerals"],
  localeCode?: string,
): string {
  const etDate = dateObj ? toEthiopicDate(dateObj) : undefined;

  if (!etDate) return "";

  const useLatin =
    (localeCode?.startsWith("en") ?? false) || numerals === "latn";

  const yearTokenMatch = formatStr.match(/^(\s*)(y+)(\s*)$/);
  if (yearTokenMatch) {
    const [, leading = "", yearToken, trailing = ""] = yearTokenMatch;
    const year = etDate.year.toString();
    let formattedYear: string;
    if (yearToken.length === 1) {
      formattedYear = year;
    } else if (yearToken.length === 2) {
      formattedYear = year.slice(-2).padStart(2, "0");
    } else {
      formattedYear = year.padStart(yearToken.length, "0");
    }
    return `${leading}${formattedYear}${trailing}`;
  }

  switch (formatStr) {
    case "LLLL yyyy":
    case "LLLL y":
      return `${getEtMonthName(etDate.month, useLatin)} ${etDate.year}`;

    case "LLLL":
      return getEtMonthName(etDate.month, useLatin);

    case "yyyy-MM-dd":
      return `${etDate.year}-${etDate.month
        .toString()
        .padStart(2, "0")}-${etDate.day.toString().padStart(2, "0")}`;

    case "yyyy-MM":
      return `${etDate.year}-${etDate.month.toString().padStart(2, "0")}`;

    case "d":
      return etDate.day.toString();
    case "PPP":
      return ` ${getEtMonthName(etDate.month, useLatin)} ${etDate.day}, ${etDate.year}`;
    case "PPPP":
      if (!dateObj) return "";
      return `${getEtDayName(dateObj, false, localeCode)}, ${getEtMonthName(etDate.month, useLatin)} ${
        etDate.day
      }, ${etDate.year}`;

    case "cccc":
      return dateObj ? getEtDayName(dateObj, false, localeCode) : "";
    case "cccccc":
      return dateObj ? getEtDayName(dateObj, true, localeCode) : "";

    default:
      return `${etDate.day}/${etDate.month}/${etDate.year}`;
  }
}

/**
 * Format an Ethiopic calendar date using a subset of date-fns tokens.
 *
 * Behavior specifics for Ethiopic mode:
 *
 * - Weekday names ("cccc", "cccccc") come from `Intl.DateTimeFormat` using
 *   `options.locale?.code` (default: `am-ET`). Narrow form is a single letter.
 * - Month names ("LLLL") are Amharic by default and switch to Latin
 *   transliteration when the locale code starts with `en` or when
 *   `options.numerals === 'latn'`.
 * - Time parts such as `hh:mm a` are delegated to `Intl.DateTimeFormat` with the
 *   given locale.
 * - Digits are converted to Ethiopic (Geez) when `options.numerals === 'geez'`.
 */
export function format(
  date: Date,
  formatStr: string,
  options?: DateFnsFormatOptions,
): string {
  const extendedOptions = options as DateLibOptions;

  if (formatStr.includes("hh:mm") || formatStr.includes("a")) {
    return new Intl.DateTimeFormat(extendedOptions?.locale?.code ?? "en-US", {
      hour: "numeric",
      minute: "numeric",
      hour12: formatStr.includes("a"),
    }).format(date);
  }

  const formatted = formatEthiopianDate(
    date,
    formatStr,
    extendedOptions?.numerals,
    extendedOptions?.locale?.code ?? "am-ET",
  );

  if (extendedOptions?.numerals && extendedOptions.numerals === "geez") {
    return formatted.replace(/\d+/g, (match) =>
      formatNumber(parseInt(match, 10), "geez"),
    );
  }

  return formatted;
}

export const ethMonths = [
  "መስከረም",
  "ጥቅምት",
  "ህዳር",
  "ታህሳስ",
  "ጥር",
  "የካቲት",
  "መጋቢት",
  "ሚያዚያ",
  "ግንቦት",
  "ሰኔ",
  "ሐምሌ",
  "ነሀሴ",
  "ጳጉሜ",
];
export const ethMonthsLatin = [
  "Meskerem",
  "Tikimt",
  "Hidar",
  "Tahsas",
  "Tir",
  "Yekatit",
  "Megabit",
  "Miyazya",
  "Ginbot",
  "Sene",
  "Hamle",
  "Nehase",
  "Pagumen",
];
export const shortDays = ["እ", "ሰ", "ማ", "ረ", "ሐ", "ዓ", "ቅ"];
export const longDays = ["እሁድ", "ሰኞ", "ማክሰኞ", "ረቡዕ", "ሐሙስ", "ዓርብ", "ቅዳሜ"];
